/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package bd.org.apache.commons.math.linear;

import bd.org.apache.commons.math.exception.*;
import bd.org.apache.commons.math.exception.util.LocalizedFormats;
import bd.org.apache.commons.math.util.FastMath;
import bd.org.apache.commons.math.util.MathUtils;

import java.util.ArrayList;

/**
 * Basic implementation of RealMatrix methods regardless of the underlying storage.
 * <p>All the methods implemented here use {@link #getEntry(int, int)} to access
 * matrix elements. Derived class can provide faster implementations. </p>
 *
 * @version $Id: AbstractRealMatrix.java 1244107 2012-02-14 16:17:55Z erans $
 * @since 2.0
 */
public abstract class AbstractRealMatrix extends RealLinearOperator implements RealMatrix {

    /**
     * Creates a matrix with no data
     */
    protected AbstractRealMatrix() {}

    /**
     * Create a new RealMatrix with the supplied row and column dimensions.
     *
     * @param rowDimension the number of rows in the new matrix
     * @param columnDimension the number of columns in the new matrix
     */
    protected AbstractRealMatrix(final int rowDimension, final int columnDimension) {

        if (rowDimension < 1) {
            throw new NotStrictlyPositiveException(rowDimension);
        }

        if (columnDimension < 1) {
            throw new NotStrictlyPositiveException(columnDimension);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public abstract RealMatrix createMatrix(final int rowDimension, final int columnDimension);


    /**
     * {@inheritDoc}
     */
    @Override
    public abstract RealMatrix copy();


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix add(RealMatrix m) {

        // Safety check.
        MatrixUtils.checkAdditionCompatible(this, m);

        final int        rowCount    = getRowDimension();
        final int        columnCount = getColumnDimension();
        final RealMatrix out         = createMatrix(rowCount, columnCount);

        for (int row = 0; row < rowCount; ++row) {

            for (int col = 0; col < columnCount; ++col) {

                out.setEntry(row, col, getEntry(row, col) + m.getEntry(row, col));
            }
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix subtract(final RealMatrix m) {

        // Safety check.
        MatrixUtils.checkSubtractionCompatible(this, m);

        final int        rowCount    = getRowDimension();
        final int        columnCount = getColumnDimension();
        final RealMatrix out         = createMatrix(rowCount, columnCount);

        for (int row = 0; row < rowCount; ++row) {

            for (int col = 0; col < columnCount; ++col) {

                out.setEntry(row, col, getEntry(row, col) - m.getEntry(row, col));
            }
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix scalarAdd(final double d) {

        final int        rowCount    = getRowDimension();
        final int        columnCount = getColumnDimension();
        final RealMatrix out         = createMatrix(rowCount, columnCount);

        for (int row = 0; row < rowCount; ++row) {

            for (int col = 0; col < columnCount; ++col) {

                out.setEntry(row, col, getEntry(row, col) + d);
            }
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix scalarMultiply(final double d) {

        final int        rowCount    = getRowDimension();
        final int        columnCount = getColumnDimension();
        final RealMatrix out         = createMatrix(rowCount, columnCount);

        for (int row = 0; row < rowCount; ++row) {

            for (int col = 0; col < columnCount; ++col) {

                out.setEntry(row, col, getEntry(row, col) * d);
            }
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix multiply(final RealMatrix m) {

        // Safety check.
        MatrixUtils.checkMultiplicationCompatible(this, m);

        final int        nRows = getRowDimension();
        final int        nCols = m.getColumnDimension();
        final int        nSum  = getColumnDimension();
        final RealMatrix out   = createMatrix(nRows, nCols);

        for (int row = 0; row < nRows; ++row) {

            for (int col = 0; col < nCols; ++col) {

                double sum = 0;

                for (int i = 0; i < nSum; ++i) {

                    sum += getEntry(row, i) * m.getEntry(i, col);
                }

                out.setEntry(row, col, sum);
            }
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix preMultiply(final RealMatrix m) {

        return m.multiply(this);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix power(final int p) {

        if (p < 0) {
            throw new IllegalArgumentException("p must be >= 0");
        }

        if (!isSquare()) {
            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
        }

        if (p == 0) {
            return MatrixUtils.createRealIdentityMatrix(this.getRowDimension());
        }

        if (p == 1) {
            return this.copy();
        }

        final int power = p - 1;

        /*
         * Only log_2(p) operations is used by doing as follows:
         * 5^214 = 5^128 * 5^64 * 5^16 * 5^4 * 5^2
         *
         * In general, the same approach is used for A^p.
         */

        final char[]             binaryRepresentation = Integer.toBinaryString(power).toCharArray();
        final ArrayList<Integer> nonZeroPositions     = new ArrayList<Integer>();
        int                      maxI                 = -1;

        for (int i = 0; i < binaryRepresentation.length; ++i) {

            if (binaryRepresentation[i] == '1') {

                final int pos = binaryRepresentation.length - i - 1;

                nonZeroPositions.add(pos);

                // The positions are taken in turn, so maxI is only changed once
                if (maxI == -1) {
                    maxI = pos;
                }
            }
        }

        RealMatrix[] results = new RealMatrix[maxI + 1];

        results[0] = this.copy();

        for (int i = 1; i <= maxI; ++i) {

            results[i] = results[i - 1].multiply(results[i - 1]);
        }

        RealMatrix result = this.copy();

        for (Integer i : nonZeroPositions) {

            result = result.multiply(results[i]);
        }

        return result;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double[][] getData() {

        final double[][] data = new double[getRowDimension()][getColumnDimension()];

        for (int i = 0; i < data.length; ++i) {

            final double[] dataI = data[i];

            for (int j = 0; j < dataI.length; ++j) {

                dataI[j] = getEntry(i, j);
            }
        }

        return data;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double getNorm() {

        return walkInColumnOrder(new RealMatrixPreservingVisitor() {

            /** Last row index. */
            private double endRow;

            /** Sum of absolute values on one column. */
            private double columnSum;

            /** Maximal sum across all columns. */
            private double maxColSum;

            /** {@inheritDoc} */
            @Override
            public void start(final int rows, final int columns, final int startRow,
                              final int endRow, final int startColumn, final int endColumn) {

                this.endRow = endRow;
                columnSum   = 0;
                maxColSum   = 0;
            }

            /** {@inheritDoc} */
            @Override
            public void visit(final int row, final int column, final double value) {

                columnSum += FastMath.abs(value);

                if (row == endRow) {

                    maxColSum = FastMath.max(maxColSum, columnSum);
                    columnSum = 0;
                }
            }

            /** {@inheritDoc} */
            @Override
            public double end() {

                return maxColSum;
            }
        });
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double getFrobeniusNorm() {

        return walkInOptimizedOrder(new RealMatrixPreservingVisitor() {

            /** Sum of squared entries. */
            private double sum;

            /** {@inheritDoc} */
            @Override
            public void start(final int rows, final int columns, final int startRow,
                              final int endRow, final int startColumn, final int endColumn) {

                sum = 0;
            }

            /** {@inheritDoc} */
            @Override
            public void visit(final int row, final int column, final double value) {

                sum += value * value;
            }

            /** {@inheritDoc} */
            @Override
            public double end() {

                return FastMath.sqrt(sum);
            }
        });
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix getSubMatrix(final int startRow, final int endRow, final int startColumn,
                                   final int endColumn) {

        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);

        final RealMatrix subMatrix = createMatrix(endRow - startRow + 1,
                                         endColumn - startColumn + 1);

        for (int i = startRow; i <= endRow; ++i) {

            for (int j = startColumn; j <= endColumn; ++j) {

                subMatrix.setEntry(i - startRow, j - startColumn, getEntry(i, j));
            }
        }

        return subMatrix;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix getSubMatrix(final int[] selectedRows, final int[] selectedColumns) {

        // safety checks
        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);

        // copy entries
        final RealMatrix subMatrix = createMatrix(selectedRows.length, selectedColumns.length);

        subMatrix.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {

            /** {@inheritDoc} */
            @Override
            public double visit(final int row, final int column, final double value) {

                return getEntry(selectedRows[row], selectedColumns[column]);
            }

        });

        return subMatrix;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void copySubMatrix(final int startRow, final int endRow, final int startColumn,
                              final int endColumn, final double[][] destination) {

        // safety checks
        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);

        final int rowsCount    = endRow + 1 - startRow;
        final int columnsCount = endColumn + 1 - startColumn;

        if ((destination.length < rowsCount) || (destination[0].length < columnsCount)) {

            throw new MatrixDimensionMismatchException(destination.length, destination[0].length,
                    rowsCount, columnsCount);
        }

        // copy entries
        walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() {

            /** Initial row index. */
            private int startRow;

            /** Initial column index. */
            private int startColumn;

            /** {@inheritDoc} */
            @Override
            public void start(final int rows, final int columns, final int startRow,
                              final int endRow, final int startColumn, final int endColumn) {

                this.startRow    = startRow;
                this.startColumn = startColumn;
            }

            /** {@inheritDoc} */
            @Override
            public void visit(final int row, final int column, final double value) {

                destination[row - startRow][column - startColumn] = value;
            }

        }, startRow, endRow, startColumn, endColumn);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void copySubMatrix(int[] selectedRows, int[] selectedColumns, double[][] destination) {

        // safety checks
        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);

        if ((destination.length < selectedRows.length)
                || (destination[0].length < selectedColumns.length)) {

            throw new MatrixDimensionMismatchException(destination.length, destination[0].length,
                    selectedRows.length, selectedColumns.length);
        }

        // copy entries
        for (int i = 0; i < selectedRows.length; i++) {

            final double[] destinationI = destination[i];

            for (int j = 0; j < selectedColumns.length; j++) {

                destinationI[j] = getEntry(selectedRows[i], selectedColumns[j]);
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setSubMatrix(final double[][] subMatrix, final int row, final int column)
            throws NoDataException, DimensionMismatchException, NullArgumentException {

        MathUtils.checkNotNull(subMatrix);

        final int nRows = subMatrix.length;

        if (nRows == 0) {
            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_ROW);
        }

        final int nCols = subMatrix[0].length;

        if (nCols == 0) {
            throw new NoDataException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
        }

        for (int r = 1; r < nRows; ++r) {

            if (subMatrix[r].length != nCols) {
                throw new DimensionMismatchException(nCols, subMatrix[r].length);
            }
        }

        MatrixUtils.checkRowIndex(this, row);
        MatrixUtils.checkColumnIndex(this, column);
        MatrixUtils.checkRowIndex(this, nRows + row - 1);
        MatrixUtils.checkColumnIndex(this, nCols + column - 1);

        for (int i = 0; i < nRows; ++i) {

            for (int j = 0; j < nCols; ++j) {

                setEntry(row + i, column + j, subMatrix[i][j]);
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix getRowMatrix(final int row) {

        MatrixUtils.checkRowIndex(this, row);

        final int        nCols = getColumnDimension();
        final RealMatrix out   = createMatrix(1, nCols);

        for (int i = 0; i < nCols; ++i) {

            out.setEntry(0, i, getEntry(row, i));
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setRowMatrix(final int row, final RealMatrix matrix) {

        MatrixUtils.checkRowIndex(this, row);

        final int nCols = getColumnDimension();

        if ((matrix.getRowDimension() != 1) || (matrix.getColumnDimension() != nCols)) {

            throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
                    matrix.getColumnDimension(), 1, nCols);
        }

        for (int i = 0; i < nCols; ++i) {

            setEntry(row, i, matrix.getEntry(0, i));
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix getColumnMatrix(final int column) {

        MatrixUtils.checkColumnIndex(this, column);

        final int        nRows = getRowDimension();
        final RealMatrix out   = createMatrix(nRows, 1);

        for (int i = 0; i < nRows; ++i) {

            out.setEntry(i, 0, getEntry(i, column));
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setColumnMatrix(final int column, final RealMatrix matrix) {

        MatrixUtils.checkColumnIndex(this, column);

        final int nRows = getRowDimension();

        if ((matrix.getRowDimension() != nRows) || (matrix.getColumnDimension() != 1)) {

            throw new MatrixDimensionMismatchException(matrix.getRowDimension(),
                    matrix.getColumnDimension(), nRows, 1);
        }

        for (int i = 0; i < nRows; ++i) {

            setEntry(i, column, matrix.getEntry(i, 0));
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealVector getRowVector(final int row) {

        return new ArrayRealVector(getRow(row), false);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setRowVector(final int row, final RealVector vector) {

        MatrixUtils.checkRowIndex(this, row);

        final int nCols = getColumnDimension();

        if (vector.getDimension() != nCols) {
            throw new MatrixDimensionMismatchException(1, vector.getDimension(), 1, nCols);
        }

        for (int i = 0; i < nCols; ++i) {

            setEntry(row, i, vector.getEntry(i));
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealVector getColumnVector(final int column) {

        return new ArrayRealVector(getColumn(column), false);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setColumnVector(final int column, final RealVector vector) {

        MatrixUtils.checkColumnIndex(this, column);

        final int nRows = getRowDimension();

        if (vector.getDimension() != nRows) {
            throw new MatrixDimensionMismatchException(vector.getDimension(), 1, nRows, 1);
        }

        for (int i = 0; i < nRows; ++i) {

            setEntry(i, column, vector.getEntry(i));
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double[] getRow(final int row) {

        MatrixUtils.checkRowIndex(this, row);

        final int      nCols = getColumnDimension();
        final double[] out   = new double[nCols];

        for (int i = 0; i < nCols; ++i) {

            out[i] = getEntry(row, i);
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setRow(final int row, final double[] array) {

        MatrixUtils.checkRowIndex(this, row);

        final int nCols = getColumnDimension();

        if (array.length != nCols) {
            throw new MatrixDimensionMismatchException(1, array.length, 1, nCols);
        }

        for (int i = 0; i < nCols; ++i) {

            setEntry(row, i, array[i]);
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double[] getColumn(final int column) {

        MatrixUtils.checkColumnIndex(this, column);

        final int      nRows = getRowDimension();
        final double[] out   = new double[nRows];

        for (int i = 0; i < nRows; ++i) {

            out[i] = getEntry(i, column);
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void setColumn(final int column, final double[] array) {

        MatrixUtils.checkColumnIndex(this, column);

        final int nRows = getRowDimension();

        if (array.length != nRows) {
            throw new MatrixDimensionMismatchException(array.length, 1, nRows, 1);
        }

        for (int i = 0; i < nRows; ++i) {

            setEntry(i, column, array[i]);
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public abstract double getEntry(int row, int column);


    /**
     * {@inheritDoc}
     */
    @Override
    public abstract void setEntry(int row, int column, double value);


    /**
     * {@inheritDoc}
     */
    @Override
    public void addToEntry(int row, int column, double increment) {

        MatrixUtils.checkMatrixIndex(this, row, column);
        setEntry(row, column, getEntry(row, column) + increment);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void multiplyEntry(int row, int column, double factor) {

        MatrixUtils.checkMatrixIndex(this, row, column);
        setEntry(row, column, getEntry(row, column) * factor);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealMatrix transpose() {

        final int        nRows = getRowDimension();
        final int        nCols = getColumnDimension();
        final RealMatrix out   = createMatrix(nCols, nRows);

        walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() {

            /** {@inheritDoc} */
            @Override
            public void visit(final int row, final int column, final double value) {

                out.setEntry(column, row, value);
            }

        });

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSquare() {

        return getColumnDimension() == getRowDimension();
    }


    /** {@inheritDoc} */

    /**
     * Returns the number of rows of this matrix.
     *
     * @return the number of rows.
     */
    @Override
    public abstract int getRowDimension();


    /**
     * Returns the number of columns of this matrix.
     *
     * @return the number of columns.
     */
    @Override
    public abstract int getColumnDimension();


    /**
     * {@inheritDoc}
     */
    @Override
    public double getTrace() {

        final int nRows = getRowDimension();
        final int nCols = getColumnDimension();

        if (nRows != nCols) {
            throw new NonSquareMatrixException(nRows, nCols);
        }

        double trace = 0;

        for (int i = 0; i < nRows; ++i) {

            trace += getEntry(i, i);
        }

        return trace;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double[] operate(final double[] v) {

        final int nRows = getRowDimension();
        final int nCols = getColumnDimension();

        if (v.length != nCols) {
            throw new DimensionMismatchException(v.length, nCols);
        }

        final double[] out = new double[nRows];

        for (int row = 0; row < nRows; ++row) {

            double sum = 0;

            for (int i = 0; i < nCols; ++i) {

                sum += getEntry(row, i) * v[i];
            }

            out[row] = sum;
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealVector operate(final RealVector v) {

        try {
            return new ArrayRealVector(operate(((ArrayRealVector) v).getDataRef()), false);
        } catch (ClassCastException cce) {

            final int nRows = getRowDimension();
            final int nCols = getColumnDimension();

            if (v.getDimension() != nCols) {
                throw new DimensionMismatchException(v.getDimension(), nCols);
            }

            final double[] out = new double[nRows];

            for (int row = 0; row < nRows; ++row) {

                double sum = 0;

                for (int i = 0; i < nCols; ++i) {

                    sum += getEntry(row, i) * v.getEntry(i);
                }

                out[row] = sum;
            }

            return new ArrayRealVector(out, false);
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double[] preMultiply(final double[] v) {

        final int nRows = getRowDimension();
        final int nCols = getColumnDimension();

        if (v.length != nRows) {
            throw new DimensionMismatchException(v.length, nRows);
        }

        final double[] out = new double[nCols];

        for (int col = 0; col < nCols; ++col) {

            double sum = 0;

            for (int i = 0; i < nRows; ++i) {

                sum += getEntry(i, col) * v[i];
            }

            out[col] = sum;
        }

        return out;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public RealVector preMultiply(final RealVector v) {

        try {
            return new ArrayRealVector(preMultiply(((ArrayRealVector) v).getDataRef()), false);
        } catch (ClassCastException cce) {

            final int nRows = getRowDimension();
            final int nCols = getColumnDimension();

            if (v.getDimension() != nRows) {
                throw new DimensionMismatchException(v.getDimension(), nRows);
            }

            final double[] out = new double[nCols];

            for (int col = 0; col < nCols; ++col) {

                double sum = 0;

                for (int i = 0; i < nRows; ++i) {

                    sum += getEntry(i, col) * v.getEntry(i);
                }

                out[col] = sum;
            }

            return new ArrayRealVector(out, false);
        }
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInRowOrder(final RealMatrixChangingVisitor visitor) {

        final int rows    = getRowDimension();
        final int columns = getColumnDimension();

        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);

        for (int row = 0; row < rows; ++row) {

            for (int column = 0; column < columns; ++column) {

                final double oldValue = getEntry(row, column);
                final double newValue = visitor.visit(row, column, oldValue);

                setEntry(row, column, newValue);
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor) {

        final int rows    = getRowDimension();
        final int columns = getColumnDimension();

        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);

        for (int row = 0; row < rows; ++row) {

            for (int column = 0; column < columns; ++column) {

                visitor.visit(row, column, getEntry(row, column));
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInRowOrder(final RealMatrixChangingVisitor visitor, final int startRow,
                                 final int endRow, final int startColumn, final int endColumn) {

        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
        visitor.start(getRowDimension(), getColumnDimension(), startRow, endRow, startColumn,
                      endColumn);

        for (int row = startRow; row <= endRow; ++row) {

            for (int column = startColumn; column <= endColumn; ++column) {

                final double oldValue = getEntry(row, column);
                final double newValue = visitor.visit(row, column, oldValue);

                setEntry(row, column, newValue);
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor, final int startRow,
                                 final int endRow, final int startColumn, final int endColumn) {

        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
        visitor.start(getRowDimension(), getColumnDimension(), startRow, endRow, startColumn,
                      endColumn);

        for (int row = startRow; row <= endRow; ++row) {

            for (int column = startColumn; column <= endColumn; ++column) {

                visitor.visit(row, column, getEntry(row, column));
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInColumnOrder(final RealMatrixChangingVisitor visitor) {

        final int rows    = getRowDimension();
        final int columns = getColumnDimension();

        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);

        for (int column = 0; column < columns; ++column) {

            for (int row = 0; row < rows; ++row) {

                final double oldValue = getEntry(row, column);
                final double newValue = visitor.visit(row, column, oldValue);

                setEntry(row, column, newValue);
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor) {

        final int rows    = getRowDimension();
        final int columns = getColumnDimension();

        visitor.start(rows, columns, 0, rows - 1, 0, columns - 1);

        for (int column = 0; column < columns; ++column) {

            for (int row = 0; row < rows; ++row) {

                visitor.visit(row, column, getEntry(row, column));
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInColumnOrder(final RealMatrixChangingVisitor visitor, final int startRow,
                                    final int endRow, final int startColumn, final int endColumn) {

        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
        visitor.start(getRowDimension(), getColumnDimension(), startRow, endRow, startColumn,
                      endColumn);

        for (int column = startColumn; column <= endColumn; ++column) {

            for (int row = startRow; row <= endRow; ++row) {

                final double oldValue = getEntry(row, column);
                final double newValue = visitor.visit(row, column, oldValue);

                setEntry(row, column, newValue);
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor, final int startRow,
                                    final int endRow, final int startColumn, final int endColumn) {

        MatrixUtils.checkSubMatrixIndex(this, startRow, endRow, startColumn, endColumn);
        visitor.start(getRowDimension(), getColumnDimension(), startRow, endRow, startColumn,
                      endColumn);

        for (int column = startColumn; column <= endColumn; ++column) {

            for (int row = startRow; row <= endRow; ++row) {

                visitor.visit(row, column, getEntry(row, column));
            }
        }

        return visitor.end();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor) {

        return walkInRowOrder(visitor);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor) {

        return walkInRowOrder(visitor);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor, final int startRow,
                                       final int endRow, final int startColumn,
                                       final int endColumn) {

        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor,
                                       final int startRow, final int endRow, final int startColumn,
                                       final int endColumn) {

        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
    }


    /**
     * Get a string representation for this matrix.
     *
     * @return a string representation for this matrix
     */
    @Override
    public String toString() {

        final int          nRows          = getRowDimension();
        final int          nCols          = getColumnDimension();
        final StringBuffer res            = new StringBuffer();
        String             fullClassName  = getClass().getName();
        String             shortClassName = fullClassName.substring(fullClassName.lastIndexOf('.')
                                                + 1);

        res.append(shortClassName).append("{");

        for (int i = 0; i < nRows; ++i) {

            if (i > 0) {
                res.append(",");
            }

            res.append("{");

            for (int j = 0; j < nCols; ++j) {

                if (j > 0) {
                    res.append(",");
                }

                res.append(getEntry(i, j));
            }

            res.append("}");
        }

        res.append("}");

        return res.toString();
    }


    /**
     * Returns true iff <code>object</code> is a
     * <code>RealMatrix</code> instance with the same dimensions as this
     * and all corresponding matrix entries are equal.
     *
     * @param object the object to test equality against.
     * @return true if object equals this
     */
    @Override
    public boolean equals(final Object object) {

        if (object == this) {
            return true;
        }

        if (object instanceof RealMatrix == false) {
            return false;
        }

        RealMatrix m     = (RealMatrix) object;
        final int  nRows = getRowDimension();
        final int  nCols = getColumnDimension();

        if ((m.getColumnDimension() != nCols) || (m.getRowDimension() != nRows)) {
            return false;
        }

        for (int row = 0; row < nRows; ++row) {

            for (int col = 0; col < nCols; ++col) {

                if (getEntry(row, col) != m.getEntry(row, col)) {
                    return false;
                }
            }
        }

        return true;
    }


    /**
     * Computes a hashcode for the matrix.
     *
     * @return hashcode for matrix
     */
    @Override
    public int hashCode() {

        int       ret   = 7;
        final int nRows = getRowDimension();
        final int nCols = getColumnDimension();

        ret = ret * 31 + nRows;
        ret = ret * 31 + nCols;

        for (int row = 0; row < nRows; ++row) {

            for (int col = 0; col < nCols; ++col) {

                ret = ret * 31
                      + (11 * (row + 1) + 17 * (col + 1)) * MathUtils.hash(getEntry(row, col));
            }
        }

        return ret;
    }
}
